iT邦幫忙

2025 iThome 鐵人賽

DAY 2
0
Rust

大家一起跟Rust當好朋友吧!系列 第 2

Day 2: 變數、資料型別與常數:強型別的第一次接觸

  • 分享至 

  • xImage
  •  

嗨嗨!大家好!歡迎回到 Rust 三十天挑戰的第二天!

昨天我們成功跑出了第一個 Rust 程式,不知道大家有沒有嘗試來完成那個小挑戰呢?如果還沒有,別擔心,今天我們也寫到昨天的小挑戰內容,之後你如果願意開始做會更容易!

今天要來探討 Rust 的變數、資料型別和常數。對於習慣了 C# 或其他高階語言的我們來說,Rust 的型別系統既熟悉又陌生。熟悉的是它同樣是強型別語言,陌生的是Rust語言本身對於變數的可變性(mutability)有著非常嚴格的要求。

其實,剛開始接觸這些概念時我也有點不習慣。畢竟在 C# 中,我們很少需要特別思考變數是否可變,雖然在clean code 有提到說,後天改值,是一個code smell,所以在開發過程中就要很依賴開發人員的潔癖程度,但在 Rust 中,這卻是一個核心概念。這也是我對於它特別有興趣的一個原因!

變數宣告:let

在 Rust 中,我們使用 let 關鍵字來宣告變數。讓我們先來看一個簡單的例子:

fn main() {
    let x = 5;
    println!("x 的值是:{}", x);
}

這看起來很簡單對吧?但這裡有個重要的概念:Rust 的變數預設是不可變的(immutable)

如果我們嘗試修改 x 的值:

fn main() {
    let x = 5;
    println!("x 的值是:{}", x);
    
    x = 6; // 這行會編譯錯誤!
    println!("x 的值是:{}", x);
}

編譯器會很不客氣地告訴你:

error[E0384]: cannot assign twice to immutable variable `x`

可變變數:mut 關鍵字

如果我們真的需要修改變數的值,就必須明確地使用 mut 關鍵字:

fn main() {
    let mut x = 5;
    println!("x 的值是:{}", x);
    
    x = 6; // 現在這樣就可以了!
    println!("x 的值是:{}", x);
}

你可能會想:「這樣不是很麻煩嗎?為什麼不讓變數預設就可以修改?」

這個設計背後的哲學很有趣:Rust 鼓勵我們優先使用不可變的變數。這樣做有幾個好處:

  1. 程式碼更容易理解:當你看到一個沒有 mut 的變數,你就知道它在整個作用域內不會改變
  2. 避免意外的修改:減少因為不小心修改變數而產生的 bug
  3. 併發安全:不可變的資料天生就是執行緒安全的

變數遮蔽(Shadowing)

Rust 還有一個有趣的特性叫做「變數遮蔽」,允許我們重新宣告同名的變數:

fn main() {
    let x = 5;
    println!("第一個 x:{}", x);
    
    let x = x + 1; // 用新的值遮蔽原本的 x
    println!("第二個 x:{}", x);
    
    let x = "現在 x 是字串了!"; // 甚至可以改變型別
    println!("第三個 x:{}", x);
}

這和使用 mut 不同。Shadowing 實際上是建立了一個新的變數,只是恰好使用了相同的名字。這樣我們甚至可以改變變數的型別,這在 mut 變數中是不被允許的。

Rust 的資料型別

Rust 是一個靜態型別語言,這意味著所有變數的型別都必須在編譯時確定。不過別擔心,Rust 的型別推導(type inference)功能非常強大,很多時候我們不需要明確指定型別。

純量型別(Scalar Types)

純量型別代表單一的值,Rust 有四種主要的純量型別:

1. 整數型別

Rust 提供了多種不同大小的整數型別:

長度 有號 無號
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize
fn main() {
    let small_number: u8 = 255;
    let big_number: i64 = -9223372036854775808;
    let arch_dependent: usize = 42; // 在 64-bit 系統上相當於 u64
    
    // Rust 會自動推導型別,預設是 i32
    let default_integer = 42;
    
    println!("各種數字:{}, {}, {}, {}", 
             small_number, big_number, arch_dependent, default_integer);
}

你也可以用不同的進位制來表示數字:

fn main() {
    let decimal = 98_222;        // 十進位,可以用 _ 增加可讀性
    let hex = 0xff;              // 十六進位
    let octal = 0o77;            // 八進位
    let binary = 0b1111_0000;    // 二進位
    let byte = b'A';             // 位元組(只適用於 u8)
    
    println!("各種表示法:{}, {}, {}, {}, {}", 
             decimal, hex, octal, binary, byte);
}

2. 浮點數型別

Rust 有兩種浮點數型別:f32f64(預設):

fn main() {
    let pi: f32 = 3.14159;      // 單精度
    let e = 2.718281828;         // 雙精度(預設)
    
    println!("π ≈ {}, e ≈ {}", pi, e);
}

3. 布林型別

布林型別只有兩個可能的值:truefalse

fn main() {
    let is_rust_awesome = true;
    let is_learning_fun: bool = false; // 明確指定型別
    
    println!("Rust 很棒嗎?{}", is_rust_awesome);
    println!("學習有趣嗎?{}", is_learning_fun);
}

4. 字元型別

Rust 的 char 型別代表一個 Unicode 純量值,使用單引號:

fn main() {
    let letter = 'A';
    let emoji = '😊';
    let chinese = '中';
    
    println!("字元:{}, {}, {}", letter, emoji, chinese);
}

複合型別(Compound Types)

複合型別可以將多個值組合成一個型別。Rust 有兩種原始的複合型別:

1. 元組(Tuple)

元組可以將不同型別的值組合在一起:

fn main() {
    let person: (String, i32, bool) = (String::from("Alice"), 25, true);
    
    // 解構賦值
    let (name, age, is_student) = person;
    
    println!("{} 今年 {} 歲,是學生:{}", name, age, is_student);
    
    // 也可以用索引存取
    let another_tuple = ("Bob", 30, false);
    println!("第一個元素:{}", another_tuple.0);
    println!("第二個元素:{}", another_tuple.1);
    println!("第三個元素:{}", another_tuple.2);
}

2. 陣列(Array)

陣列中的所有元素必須是相同型別,且長度固定:

fn main() {
    let numbers = [1, 2, 3, 4, 5];
    let months = ["一月", "二月", "三月", "四月", "五月"];
    
    // 明確指定型別和長度
    let zeros: [i32; 5] = [0; 5]; // 建立包含 5 個 0 的陣列
    
    println!("第一個數字:{}", numbers[0]);
    println!("第三個月份:{}", months[2]);
    println!("零陣列:{:?}", zeros); // {:?} 用於除錯輸出
}

注意:如果你想要動態大小的陣列,你需要使用 Vec<T>(我們之後會學到)。

常數:永不改變的值

除了變數,Rust 還提供了常數(constants),使用 const 關鍵字宣告:

const MAX_USERS: u32 = 100_000;
const PI: f64 = 3.14159265359;

fn main() {
    println!("最大用戶數:{}", MAX_USERS);
    println!("圓周率:{}", PI);
}

常數和不可變變數的差異:

  1. 常數不能使用 mut:常數永遠是不可變的
  2. 常數必須明確指定型別:不能依賴型別推導
  3. 常數必須是常數表達式:不能是函式呼叫的結果或任何在執行時計算的值
  4. 常數可以在任何作用域宣告:包括全域作用域
  5. 常數在整個程式執行期間都有效:在它們宣告的作用域內

型別轉換與型別標註

有時候我們需要明確指定變數的型別,或者進行型別轉換:

fn main() {
    // 型別標註
    let x: i32 = 42;
    let y: f64 = 3.14;
    
    // 型別轉換(casting)
    let integer = 42;
    let float = integer as f64;
    
    println!("整數:{},浮點數:{}", integer, float);
    
    // 字串轉數字(記得昨天的小挑戰嗎?)
    let number_string = "42";
    let number: i32 = number_string.parse().expect("無法解析數字");
    
    println!("從字串解析的數字:{}", number);
}

實戰練習:改進昨天的程式

讓我們用今天學到的知識來改進昨天的小挑戰:這個程式展示了今天學到的幾個重要概念:

  1. 型別轉換:使用 parse() 將字串轉換為數字
  2. 條件表達式if 在 Rust 中是表達式,可以回傳值
  3. 不同的資料型別:字串、整數、布林值
  4. 變數的使用:有些可變(mut),有些不可變

今天的收穫

今天我們學到了:

變數與可變性

  • Rust 的變數預設是不可變的
  • 使用 mut 關鍵字建立可變變數
  • 變數遮蔽(shadowing)的概念

資料型別

  • 純量型別:整數、浮點數、布林、字元
  • 複合型別:元組、陣列
  • 型別推導與明確標註

常數

  • 使用 const 宣告
  • 必須明確指定型別
  • 在編譯時就確定值

明天我們將繼續深入 Rust 的世界,學習函式和流程控制。我們會發現 Rust 中許多看似普通的語法,其實都隱藏著很有趣的設計!

今天的小挑戰

試著建立一個程式,要求使用者輸入三個數字,然後:

  1. 計算它們的平均值
  2. 找出最大值和最小值
  3. 判斷是否有任何數字是偶數

提示:你可能需要用到 f64 型別來計算平均值,以及 % 運算子來判斷偶數!

如果在實作過程中遇到任何問題,歡迎在留言區討論。型別系統剛開始可能會覺得有點嚴格,但相信我,等你習慣了之後,你會愛上這種「編譯器幫你檢查錯誤」的感覺!

我們明天見!


上一篇
Day 1: 為何選擇 Rust?我的第一行 Rust 程式碼
下一篇
Day 3: 函式與流程控制:讓程式有邏輯、有組織
系列文
大家一起跟Rust當好朋友吧!20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言